﻿namespace CRM2011FieldDataTypeConverter
{
  using System;
  using System.Collections.Generic;
  using System.Configuration;
  using System.Linq;
  using System.Reflection;
  using System.ServiceModel;
  using System.Text;
  using Microsoft.Crm.Sdk.Messages;
  using Microsoft.Xrm.Client;
  using Microsoft.Xrm.Client.Services;
  using Microsoft.Xrm.Sdk.Messages;
  using Microsoft.Xrm.Sdk.Metadata;
  using Microsoft.Xrm.Sdk;
  using Microsoft.Xrm.Sdk.Query;
  using System.Globalization;

  public class Program
  {
    private static string argURL = string.Empty;
    private static string argUsername = string.Empty;
    private static string argPassword = string.Empty;
    private static string argDomain = string.Empty;

    private static bool argSaveChanges = false;

    private static string argEntity = string.Empty;
    private static string argAttribute = string.Empty;
    private static AttributeTypeCode argType = AttributeTypeCode.String;

    private static string argBooleanTrueLabel = "Yes";
    private static string argBooleanFalseLabel = "No";
    private static bool argBooleanDefaultValue = false;

    private static string argDateTimePattern = "yyyy-MM-dd hh:mm:ss tt";
    private static DateTimeFormat argDateTimeFormat = DateTimeFormat.DateOnly;
    private static ImeMode argDateTimeImeMode = ImeMode.Auto;

    private static decimal argDecimalMaxValue = 1000000000;
    private static decimal argDecimalMinValue = 0;
    private static int argDecimalPrecision = 2;
    private static ImeMode argDecimalImeMode = ImeMode.Auto;

    private static double argDoubleMaxValue = 1000000000;
    private static double argDoubleMinValue = 0;
    private static int argDoublePrecision = 2;
    private static ImeMode argDoubleImeMode = ImeMode.Auto;

    private static int argIntegerMaxValue = 2147483647;
    private static int argIntegerMinValue = -2147483648;
    private static IntegerFormat argIntegerFormat = IntegerFormat.None;

    private static ImeMode argMemoImeMode = ImeMode.Auto;
    private static int argMemoMaxLength = 2000;

    private static double argMoneyMaxValue = 1000000000;
    private static double argMoneyMinValue = 0;
    private static int argMoneyPrecision = 2;
    private static int argMoneyPrecisionSource = 2;
    private static ImeMode argMoneyImeMode = ImeMode.Auto;

    private static StringFormat argStringFormat = StringFormat.Text;
    private static int argStringMaxLength = 100;
    private static ImeMode argStringImeMode = ImeMode.Auto;

    private static List<Conversion> allowedConversions = new List<Conversion>();

    static void Main(string[] args)
    {
      string parametersValid = GetParameters(args);

      Console.WriteLine(string.Format("CRMFDTC : CRM 2011 Field Data Type Convertor [Version {0}]", Assembly.GetExecutingAssembly().GetName().Version.ToString()));
      Console.WriteLine("Running (/savechanges = {0})", argSaveChanges);
      Console.WriteLine();

      if (string.IsNullOrEmpty(parametersValid))
      {
        Console.WriteLine("Have you backed up your CRM database (Y/N)?");

        if (Console.ReadKey().Key == ConsoleKey.Y)
        {
          Console.WriteLine();
          using (OrganizationService service = new OrganizationService(new CrmConnection(new ConnectionStringSettings("CRM", Connection))))
          {
            Console.Write("Retrieving entity... ");
            RetrieveEntityRequest retrieveEntityRequest = new RetrieveEntityRequest
            {
              EntityFilters = EntityFilters.Attributes,
              LogicalName = argEntity,
              RetrieveAsIfPublished = false
            };
            RetrieveEntityResponse retrieveEntityResponse = null;
            try
            {
              retrieveEntityResponse = (RetrieveEntityResponse)service.Execute(retrieveEntityRequest);
              Console.WriteLine("Done");
            }
            catch (FaultException fe)
            {
              Console.WriteLine(fe.Message);
            }
            catch (Exception ex)
            {
              Console.WriteLine(ex.Message);
            }

            if (retrieveEntityResponse != null)
            {
              EntityMetadata entityMetadata = retrieveEntityResponse.EntityMetadata;

              Console.Write("Retrieving attribute... ");
              AttributeMetadata attributeMetadata = entityMetadata.Attributes.FirstOrDefault(a => a.LogicalName == argAttribute);
              if (attributeMetadata == null)
              {
                throw new FaultException(string.Format("Could not find field {0} on entity {1}", argAttribute, argEntity));
              }
              else
              {
                Console.WriteLine("Done");
              }

              Console.Write("Checking conversion compatability ({0} to {1})... ", attributeMetadata.AttributeType, argType);
              Conversion conversion = null;
              if (attributeMetadata.AttributeType == argType)
              {
                Console.WriteLine("Attribute is already a {0} attribute.", argType);
              }
              else
              {
                conversion = AllowedConversions.FirstOrDefault(c => c.From == attributeMetadata.AttributeType && c.To == argType);
                Console.WriteLine("{0}Allowed", conversion == null ? "Not " : string.Empty);
              }

              if (conversion != null)
              {
                Console.Write("Retrieving attribute dependencies... ");
                RetrieveDependenciesForDeleteRequest retrieveDependenciesForDeleteRequest = new RetrieveDependenciesForDeleteRequest
                {
                  ComponentType = 2, //Attribute
                  ObjectId = attributeMetadata.MetadataId.Value
                };
                RetrieveDependenciesForDeleteResponse retrieveDependenciesForDeleteResponse = (RetrieveDependenciesForDeleteResponse)service.Execute(retrieveDependenciesForDeleteRequest);

                if (retrieveDependenciesForDeleteResponse.EntityCollection.Entities.Count == 0)
                {
                  Console.WriteLine("None");
                }
                else
                {
                  Console.WriteLine("{0} dependenc{1}.", retrieveDependenciesForDeleteResponse.EntityCollection.Entities.Count, retrieveDependenciesForDeleteResponse.EntityCollection.Entities.Count == 1 ? "y" : "ies");
                }

                Console.WriteLine();
                if (retrieveDependenciesForDeleteResponse.EntityCollection.Entities.Count == 0 || !argSaveChanges)
                {
                  Console.Write("Retrieving all entity instances... ");
                  QueryExpression queryExpression = new QueryExpression
                  {
                    EntityName = argEntity,
                    ColumnSet = new ColumnSet(new string[] { entityMetadata.PrimaryIdAttribute, argAttribute })
                  };
                  EntityCollection entityCollection = service.RetrieveMultiple(queryExpression);
                  Console.WriteLine("Done");

                  Console.Write("Caching attribute data... ");
                  List<EntityInstance> entityInstances = new List<EntityInstance>();
                  foreach (Entity entity in entityCollection.Entities)
                  {
                    object newValue = null;
                    string result = string.Empty;
                    try
                    {
                      newValue = conversion.ConvertValue(entity, argAttribute);
                    }
                    catch (FormatException fe)
                    {
                      result = fe.Message;
                    }
                    catch (InvalidCastException ice)
                    {
                      result = ice.Message;
                    }
                    catch (Exception e)
                    {
                      result = e.Message;
                    }
                    entityInstances.Add(new EntityInstance((Guid)entity.Attributes[entityMetadata.PrimaryIdAttribute], GetAttribute(entity, argAttribute), newValue, result));
                  }
                  Console.WriteLine("Done");

                  Console.WriteLine();
                  if (argSaveChanges)
                  {
                    Console.Write("Deleting attribute... ");
                    DeleteAttributeRequest deleteAttributeRequest = new DeleteAttributeRequest
                    {
                      EntityLogicalName = argEntity,
                      LogicalName = argAttribute
                    };
                    DeleteAttributeResponse deleteAttributeResponse = (DeleteAttributeResponse)service.Execute(deleteAttributeRequest);
                    Console.WriteLine("Done");
                  }
                  else
                  {
                    Console.WriteLine("Skipping deleting attribute (/savechanges = false)");
                  }

                  Console.Write("Configuring new attribute... ");
                  AttributeMetadata newAttribute = null;
                  switch (argType)
                  {
                    case AttributeTypeCode.Boolean:
                      newAttribute = new BooleanAttributeMetadata
                      {
                        SchemaName = attributeMetadata.SchemaName,
                        DisplayName = attributeMetadata.DisplayName,
                        RequiredLevel = attributeMetadata.RequiredLevel,
                        Description = attributeMetadata.Description,
                        OptionSet = new BooleanOptionSetMetadata(
                          new OptionMetadata(new Label(argBooleanTrueLabel, attributeMetadata.DisplayName.UserLocalizedLabel.LanguageCode), 1),
                          new OptionMetadata(new Label(argBooleanFalseLabel, attributeMetadata.DisplayName.UserLocalizedLabel.LanguageCode), 0)
                        ),
                        DefaultValue = argBooleanDefaultValue
                      };
                      Console.WriteLine("Done");
                      break;
                    case AttributeTypeCode.DateTime:
                      newAttribute = new DateTimeAttributeMetadata
                      {
                        SchemaName = attributeMetadata.SchemaName,
                        DisplayName = attributeMetadata.DisplayName,
                        RequiredLevel = attributeMetadata.RequiredLevel,
                        Description = attributeMetadata.Description,
                        Format = argDateTimeFormat,
                        ImeMode = argDateTimeImeMode
                      };
                      Console.WriteLine("Done");
                      break;
                    case AttributeTypeCode.Decimal:
                      newAttribute = new DecimalAttributeMetadata
                      {
                        SchemaName = attributeMetadata.SchemaName,
                        DisplayName = attributeMetadata.DisplayName,
                        RequiredLevel = attributeMetadata.RequiredLevel,
                        Description = attributeMetadata.Description,
                        MaxValue = argDecimalMaxValue,
                        MinValue = argDecimalMinValue,
                        Precision = argDecimalPrecision,
                        ImeMode = argDecimalImeMode
                      };
                      Console.WriteLine("Done");
                      break;
                    case AttributeTypeCode.Double:
                      newAttribute = new DoubleAttributeMetadata
                      {
                        SchemaName = attributeMetadata.SchemaName,
                        DisplayName = attributeMetadata.DisplayName,
                        RequiredLevel = attributeMetadata.RequiredLevel,
                        Description = attributeMetadata.Description,
                        MaxValue = argDoubleMaxValue,
                        MinValue = argDoubleMinValue,
                        Precision = argDoublePrecision,
                        ImeMode = argDoubleImeMode
                      };
                      Console.WriteLine("Done");
                      break;
                    case AttributeTypeCode.Integer:
                      newAttribute = new IntegerAttributeMetadata
                      {
                        SchemaName = attributeMetadata.SchemaName,
                        DisplayName = attributeMetadata.DisplayName,
                        RequiredLevel = attributeMetadata.RequiredLevel,
                        Description = attributeMetadata.Description,
                        Format = argIntegerFormat,
                        MaxValue = argIntegerMaxValue,
                        MinValue = argIntegerMinValue
                      };
                      Console.WriteLine("Done");
                      break;
                    case AttributeTypeCode.Memo:
                      newAttribute = new MemoAttributeMetadata
                      {
                        SchemaName = attributeMetadata.SchemaName,
                        DisplayName = attributeMetadata.DisplayName,
                        RequiredLevel = attributeMetadata.RequiredLevel,
                        Description = attributeMetadata.Description,
                        Format = StringFormat.TextArea,
                        MaxLength = argMemoMaxLength,
                        ImeMode = argMemoImeMode
                      };
                      Console.WriteLine("Done");
                      break;
                    case AttributeTypeCode.Money:
                      newAttribute = new MoneyAttributeMetadata
                      {
                        SchemaName = attributeMetadata.SchemaName,
                        DisplayName = attributeMetadata.DisplayName,
                        RequiredLevel = attributeMetadata.RequiredLevel,
                        Description = attributeMetadata.Description,
                        MaxValue = argMoneyMaxValue,
                        MinValue = argMoneyMinValue,
                        Precision = argMoneyPrecision,
                        PrecisionSource = argMoneyPrecisionSource,
                        ImeMode = argMoneyImeMode
                      };
                      Console.WriteLine("Done");
                      break;
                    case AttributeTypeCode.String:
                      newAttribute = new StringAttributeMetadata
                      {
                        SchemaName = attributeMetadata.SchemaName,
                        DisplayName = attributeMetadata.DisplayName,
                        RequiredLevel = attributeMetadata.RequiredLevel,
                        Description = attributeMetadata.Description,
                        Format = argStringFormat,
                        MaxLength = argStringMaxLength,
                        ImeMode = argStringImeMode
                      };
                      Console.WriteLine("Done");
                      break;
                    default:
                      Console.WriteLine("Unhandled To type: {0}", argType);
                      break;
                  }

                  CreateAttributeResponse createAttributeResponse = null;
                  if (argSaveChanges)
                  {
                    Console.Write("Creating new attribute... ");
                    CreateAttributeRequest createAttributeRequest = new CreateAttributeRequest
                    {
                      Attribute = newAttribute,
                      EntityName = argEntity
                    };

                    try
                    {
                      createAttributeResponse = (CreateAttributeResponse)service.Execute(createAttributeRequest);
                      Console.WriteLine("Done");
                    }
                    catch (FaultException fe)
                    {
                      Console.WriteLine(fe.Message);
                    }
                    catch (Exception ex)
                    {
                      Console.WriteLine(ex.Message);
                    }
                  }
                  else
                  {
                    Console.WriteLine("Skipping creating new attribute (/savechanges = false)");
                  }

                  if (createAttributeResponse != null)
                  {
                    Console.Write("Publishing customisations... ");
                    PublishAllXmlRequest publishAllXmlRequest = new PublishAllXmlRequest();
                    PublishAllXmlResponse publishAllXmlResponse = null;
                    try
                    {
                      publishAllXmlResponse = (PublishAllXmlResponse)service.Execute(publishAllXmlRequest);
                      Console.WriteLine("Done");
                    }
                    catch (FaultException fe)
                    {
                      Console.WriteLine(fe.Message);
                    }
                    catch (Exception ex)
                    {
                      Console.WriteLine(ex.Message);
                    }

                    if (publishAllXmlResponse != null)
                    {
                      ExecuteMultipleRequest executeMultipleRequest = new ExecuteMultipleRequest
                      {
                        Requests = new OrganizationRequestCollection(),
                        Settings = new ExecuteMultipleSettings
                        {
                          ContinueOnError = true,
                          ReturnResponses = true
                        }
                      };

                      Console.WriteLine();
                      Console.Write("Restoring cached data... ");
                      foreach (EntityInstance entityInstance in entityInstances.Where(ei => !ei.HasFault))
                      {
                        UpdateRequest updateRequest = new UpdateRequest
                        {
                          RequestId = entityInstance.ObjectId,
                          Target = new Entity(argEntity)
                        };
                        updateRequest.Target.Attributes[entityMetadata.PrimaryIdAttribute] = entityInstance.ObjectId;
                        updateRequest.Target.Attributes[argAttribute] = entityInstance.NewValue;
                        executeMultipleRequest.Requests.Add(updateRequest);
                      }
                      Console.WriteLine("Done");

                      Console.Write("Saving changes... ");
                      ExecuteMultipleResponse executeMultipleResponse = (ExecuteMultipleResponse)service.Execute(executeMultipleRequest);
                      Console.WriteLine("Done");

                      foreach (ExecuteMultipleResponseItem executeMultipleResponseItem in executeMultipleResponse.Responses.Where(emr => emr.Fault != null))
                      {
                        EntityInstance entityInstance = entityInstances.FirstOrDefault(ei => ei.ObjectId == executeMultipleRequest.Requests[executeMultipleResponseItem.RequestIndex].RequestId);
                        entityInstance.Fault = executeMultipleResponseItem.Fault.Message;
                      }
                    }
                  }

                  Console.WriteLine();
                  Console.Write("Writing results... ");
                  List<EntityInstance> failures = entityInstances.Where(ei => ei.HasFault).ToList();
                  if (failures.Count == 0)
                  {
                    Console.WriteLine("No failures");
                  }
                  else
                  {
                    string fileName = string.Format("Failures_{0}.csv", DateTime.Now.ToString("yyyyMMddHHmmss"));
                    Console.Write("Writing {0}... ", fileName);
                    using (System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(fileName))
                    {
                      streamWriter.WriteLine("ObjectId,Value,Error");
                      foreach (EntityInstance entityInstance in failures)
                      {
                        switch (attributeMetadata.AttributeType)
                        {
                          case AttributeTypeCode.Memo:
                          case AttributeTypeCode.String:
                            streamWriter.WriteLine("{0},\"{1}\",\"{2}\"", entityInstance.ObjectId, entityInstance.OldValue, entityInstance.Fault);
                            break;
                          default:
                            streamWriter.WriteLine("{0},{1},\"{2}\"", entityInstance.ObjectId, entityInstance.OldValue, entityInstance.Fault);
                            break;
                        }
                      }
                      streamWriter.Flush();
                      streamWriter.Close();
                    }
                    Console.WriteLine("Done");
                  }
                }
              }
            }

            Console.WriteLine();
            Console.WriteLine("Press any key to continue...");
          }
        }
        else
        {
          Console.WriteLine();
          Console.WriteLine("Please backup your CRM database before continuing...");
        }
      }
      else
      {
        Console.WriteLine(parametersValid);
        Console.WriteLine();

        Console.WriteLine("Options:");

        Console.WriteLine("/url:<url>");
        Console.WriteLine("A url or path to the SDK endpoint to contact for metadata.");
        Console.WriteLine();

        Console.WriteLine("/username:<username>");
        Console.WriteLine("Username to use when connecting to the server for authentication.");
        Console.WriteLine("Short form is '/u:'.");
        Console.WriteLine();

        Console.WriteLine("/password:<password>");
        Console.WriteLine("Password to use when connecting to the server for authentication.");
        Console.WriteLine("Short form is '/p:'.");
        Console.WriteLine();

        Console.WriteLine("/domain:<domain>");
        Console.WriteLine("Domain to authenticate against when connecting to the server.");
        Console.WriteLine("Short form is '/d:'.");
        Console.WriteLine();

        Console.WriteLine("/savechanges:<savechanges>");
        Console.WriteLine("Save the changes to the database. Defaults to false.");
        Console.WriteLine("Only by supplying true, will the changes be saved to the CRM database.");
        Console.WriteLine("Used to test whether data conversion will succeed or not.");
        Console.WriteLine("Short form is '/s:'.");
        Console.WriteLine();

        Console.WriteLine("/type:<type>");
        Console.WriteLine("The type to convert the field to. Possible values are:");
        Console.WriteLine("  {0}", AttributeTypeCode.Boolean);
        Console.WriteLine("  {0}", AttributeTypeCode.DateTime);
        Console.WriteLine("  {0}", AttributeTypeCode.Decimal);
        Console.WriteLine("  {0}", AttributeTypeCode.Double);
        Console.WriteLine("  {0}", AttributeTypeCode.Integer);
        Console.WriteLine("  {0}", AttributeTypeCode.Memo);
        Console.WriteLine("  {0}", AttributeTypeCode.Money);
        Console.WriteLine("  {0}", AttributeTypeCode.String);
        Console.WriteLine("Short form is '/t:'.");
        Console.WriteLine();

        Console.WriteLine("/booleantruelabel:<booleantruelabel>");
        Console.WriteLine("The label for the true value for the new Boolean attribute.");
        Console.WriteLine("Defaults to \"Yes\".");
        Console.WriteLine();

        Console.WriteLine("/booleanfalselabel:<booleanfalselabel>");
        Console.WriteLine("The label for the false value for the new Boolean attribute.");
        Console.WriteLine("Defaults to \"No\".");
        Console.WriteLine();

        Console.WriteLine("/booleandefaultvalue:<booleandefaultvalue>");
        Console.WriteLine("The default value of the new DateTime attribute. Possible values are:");
        Console.WriteLine("  true");
        Console.WriteLine("  false");
        Console.WriteLine("Defaults to false.");
        Console.WriteLine();

        Console.WriteLine("/datetimepattern:<datetimepattern>");
        Console.WriteLine("The pattern of the date time used to convert strings to dates.");
        Console.WriteLine("Defaults to \"yyyy-MM-dd hh:mm:ss tt\".", DateTimeFormat.DateOnly);
        Console.WriteLine();

        Console.WriteLine("/datetimeformat:<datetimeformat>");
        Console.WriteLine("The format of the new DateTime attribute. Possible values are:");
        Console.WriteLine("  {0}", DateTimeFormat.DateAndTime);
        Console.WriteLine("  {0}", DateTimeFormat.DateOnly);
        Console.WriteLine("Defaults to {0}.", DateTimeFormat.DateOnly);
        Console.WriteLine();

        Console.WriteLine("/datetimeimemode:<datetimeimemode>");
        Console.WriteLine("The IME Mode of the new DateTime attribute. Possible values are:");
        Console.WriteLine("  {0}", ImeMode.Active);
        Console.WriteLine("  {0}", ImeMode.Auto);
        Console.WriteLine("  {0}", ImeMode.Disabled);
        Console.WriteLine("  {0}", ImeMode.Inactive);
        Console.WriteLine("Defaults to {0}.", ImeMode.Auto);
        Console.WriteLine();

        Console.WriteLine("/decimalmaxvalue:<decimalmaxvalue>");
        Console.WriteLine("The maximum value for the new Decimal attribute.");
        Console.WriteLine("Defaults to 1,000,000,000.");
        Console.WriteLine();

        Console.WriteLine("/decimalminvalue:<decimalminvalue>");
        Console.WriteLine("The minimum value for the new Decimal attribute.");
        Console.WriteLine("Defaults to 0.");
        Console.WriteLine();

        Console.WriteLine("/decimalprecision:<decimalprecision>");
        Console.WriteLine("The precision for the new Decimal attribute.");
        Console.WriteLine("Defaults to 2.");
        Console.WriteLine();

        Console.WriteLine("/decimalimemode:<decimalimemode>");
        Console.WriteLine("The IME Mode of the new Decimal attribute. Possible values are:");
        Console.WriteLine("  {0}", ImeMode.Active);
        Console.WriteLine("  {0}", ImeMode.Auto);
        Console.WriteLine("  {0}", ImeMode.Disabled);
        Console.WriteLine("  {0}", ImeMode.Inactive);
        Console.WriteLine("Defaults to {0}.", ImeMode.Auto);
        Console.WriteLine();

        Console.WriteLine("/doublemaxvalue:<doublemaxvalue>");
        Console.WriteLine("The maximum value for the new Double attribute.");
        Console.WriteLine("Defaults to 1,000,000,000.");
        Console.WriteLine();

        Console.WriteLine("/doubleminvalue:<doubleminvalue>");
        Console.WriteLine("The minimum value for the new Double attribute.");
        Console.WriteLine("Defaults to 0.");
        Console.WriteLine();

        Console.WriteLine("/doubleprecision:<doubleprecision>");
        Console.WriteLine("The precision for the new Double attribute.");
        Console.WriteLine("Defaults to 2.");
        Console.WriteLine();

        Console.WriteLine("/doubleimemode:<doubleimemode>");
        Console.WriteLine("The IME Mode of the new Double attribute. Possible values are:");
        Console.WriteLine("  {0}", ImeMode.Active);
        Console.WriteLine("  {0}", ImeMode.Auto);
        Console.WriteLine("  {0}", ImeMode.Disabled);
        Console.WriteLine("  {0}", ImeMode.Inactive);
        Console.WriteLine("Defaults to {0}.", ImeMode.Auto);
        Console.WriteLine();

        Console.WriteLine("/integerformat:<integerformat>");
        Console.WriteLine("The Format of the new Integer attribute. Possible values are:");
        Console.WriteLine("  {0}", IntegerFormat.Duration);
        Console.WriteLine("  {0}", IntegerFormat.Language);
        Console.WriteLine("  {0}", IntegerFormat.Locale);
        Console.WriteLine("  {0}", IntegerFormat.None);
        Console.WriteLine("  {0}", IntegerFormat.TimeZone);
        Console.WriteLine("Defaults to {0}.", IntegerFormat.None);
        Console.WriteLine();

        Console.WriteLine("/integermaxvalue:<integermaxvalue>");
        Console.WriteLine("The maximum value for the new Integer attribute.");
        Console.WriteLine("Defaults to 2,147,483,647.");
        Console.WriteLine();

        Console.WriteLine("/integerminvalue:<integerminvalue>");
        Console.WriteLine("The minimum value for the new Integer attribute.");
        Console.WriteLine("Defaults to -2,147,483,648.");
        Console.WriteLine();

        Console.WriteLine("/memoimemode:<memoimemode>");
        Console.WriteLine("The IME Mode of the new Memo attribute. Possible values are:");
        Console.WriteLine("  {0}", ImeMode.Active);
        Console.WriteLine("  {0}", ImeMode.Auto);
        Console.WriteLine("  {0}", ImeMode.Disabled);
        Console.WriteLine("  {0}", ImeMode.Inactive);
        Console.WriteLine("Defaults to {0}.", ImeMode.Auto);
        Console.WriteLine();

        Console.WriteLine("/memomaxlength:<memomaxlength>");
        Console.WriteLine("The maximum length for the new Memo attribute.");
        Console.WriteLine("Defaults to 2,000.");
        Console.WriteLine();

        Console.WriteLine("/moneymaxvalue:<moneymaxvalue>");
        Console.WriteLine("The maximum value for the new Money attribute.");
        Console.WriteLine("Defaults to 1,000,000,000.");
        Console.WriteLine();

        Console.WriteLine("/moneyminvalue:<moneyminvalue>");
        Console.WriteLine("The minimum value for the new Money attribute.");
        Console.WriteLine("Defaults to 0.");
        Console.WriteLine();

        Console.WriteLine("/moneyprecision:<moneyprecision>");
        Console.WriteLine("The precision for the new Money attribute.");
        Console.WriteLine("Defaults to 2.");
        Console.WriteLine();

        Console.WriteLine("/moneyprecisionsource:<moneyprecisionsource>");
        Console.WriteLine("The precision source for the new Money attribute. Possible values are:");
        Console.WriteLine("  0 - MoneyAttributeMetadata.Precision");
        Console.WriteLine("  1 - Organization.PricingDecimalPrecision");
        Console.WriteLine("  2 - TransactionCurrency.CurrencyPrecision");
        Console.WriteLine("Defaults to 2.");
        Console.WriteLine();

        Console.WriteLine("/moneyimemode:<moneyimemode>");
        Console.WriteLine("The IME Mode of the new Money attribute. Possible values are:");
        Console.WriteLine("  {0}", ImeMode.Active);
        Console.WriteLine("  {0}", ImeMode.Auto);
        Console.WriteLine("  {0}", ImeMode.Disabled);
        Console.WriteLine("  {0}", ImeMode.Inactive);
        Console.WriteLine("Defaults to {0}.", ImeMode.Auto);
        Console.WriteLine();

        Console.WriteLine("/stringformat:<stringformat>");
        Console.WriteLine("The Format of the new String attribute. Possible values are:");
        Console.WriteLine("  {0}", StringFormat.Email);
        Console.WriteLine("  {0}", StringFormat.Text);
        Console.WriteLine("  {0}", StringFormat.TextArea);
        Console.WriteLine("  {0}", StringFormat.TickerSymbol);
        Console.WriteLine("  {0}", StringFormat.Url);
        Console.WriteLine("Defaults to {0}.", StringFormat.Text);
        Console.WriteLine();

        Console.WriteLine("/stringmaxlength:<stringmaxlength>");
        Console.WriteLine("The maximum length for the new String attribute.");
        Console.WriteLine("Defaults to 100.");
        Console.WriteLine();

        Console.WriteLine("/stringimemode:<stringimemode>");
        Console.WriteLine("The IME Mode of the new String attribute. Possible values are:");
        Console.WriteLine("  {0}", ImeMode.Active);
        Console.WriteLine("  {0}", ImeMode.Auto);
        Console.WriteLine("  {0}", ImeMode.Disabled);
        Console.WriteLine("  {0}", ImeMode.Inactive);
        Console.WriteLine("Defaults to {0}.", ImeMode.Auto);
        Console.WriteLine();

        Console.WriteLine("/help");
        Console.WriteLine("Show this usage message.  Short form is '/?'.");
        Console.WriteLine();

        Console.WriteLine("Example:");
        Console.WriteLine("crmfdtc.exe");
        Console.WriteLine("/url:http://localhost/Organization1/XRMServices/2011/Organization.svc");
      }

      Console.ReadKey();
    }

    private static string GetParameters(string[] args)
    {
      foreach (string[] argument in args.Select(a => a.Split(new char[] { ':' }, 2)))
      {
        if (argument.Length == 2)
        {
          switch (argument[0].ToLowerInvariant())
          {
            case "/url":
              argURL = argument[1];
              break;
            case "/username":
            case "/u":
              argUsername = argument[1];
              break;
            case "/password":
            case "/p":
              argPassword = argument[1].Trim(new char[] { '"' });
              break;
            case "/domain":
            case "/d":
              argDomain = argument[1];
              break;
            case "/savechanges":
            case "/s":
              argSaveChanges = argument[1].ToLowerInvariant() == "true";
              break;
            case "/entity":
            case "/e":
              argEntity = argument[1];
              break;
            case "/attribute":
            case "/a":
              argAttribute = argument[1];
              break;
            case "/type":
            case "/t":
              switch (argument[1])
              {
                case "Boolean": argType = AttributeTypeCode.Boolean; break;
                case "DateTime": argType = AttributeTypeCode.DateTime; break;
                case "Decimal": argType = AttributeTypeCode.Decimal; break;
                case "Double": argType = AttributeTypeCode.Double; break;
                case "Integer": argType = AttributeTypeCode.Integer; break;
                case "Memo": argType = AttributeTypeCode.Memo; break;
                case "Money": argType = AttributeTypeCode.Money; break;
                case "String": argType = AttributeTypeCode.String; break;
                default:
                  return string.Format("Invalid /type: {0}", argument[1]);
              }
              break;
            case "/booleantruelabel":
              argBooleanTrueLabel = argument[1];
              break;
            case "/booleanfalselabel":
              argBooleanFalseLabel = argument[1];
              break;
            case "/booleandefaultvalue":
              argBooleanDefaultValue = argument[1].ToLowerInvariant() == "true";
              break;
            case "/datetimepattern":
              argDateTimePattern = argument[1];
              break;
            case "/datetimeformat":
              if (!Enum.TryParse<DateTimeFormat>(argument[1], true, out argDateTimeFormat))
              {
                return string.Format("Invalid /datetimeformat: {0}", argument[1]);
              }
              break;
            case "/datetimeimemode":
              if (!Enum.TryParse<ImeMode>(argument[1], true, out argDateTimeImeMode))
              {
                return string.Format("Invalid /datetimeimemode: {0}", argument[1]);
              }
              break;
            case "/decimalmaxvalue":
              if (!decimal.TryParse(argument[1], out argDecimalMaxValue))
              {
                return string.Format("Invalid /decimalmaxvalue: {0}", argument[1]);
              }
              break;
            case "/decimalminvalue":
              if (!decimal.TryParse(argument[1], out argDecimalMinValue))
              {
                return string.Format("Invalid /decimalminvalue: {0}", argument[1]);
              }
              break;
            case "/decimalprecision":
              if (!int.TryParse(argument[1], out argDecimalPrecision))
              {
                return string.Format("Invalid /decimalprecision: {0}", argument[1]);
              }
              break;
            case "/decimalimemode":
              if (!Enum.TryParse<ImeMode>(argument[1], true, out argDecimalImeMode))
              {
                return string.Format("Invalid /decimalimemode: {0}", argument[1]);
              }
              break;
            case "/doublemaxvalue":
              if (!double.TryParse(argument[1], out argDoubleMaxValue))
              {
                return string.Format("Invalid /doublemaxvalue: {0}", argument[1]);
              }
              break;
            case "/doubleminvalue":
              if (!double.TryParse(argument[1], out argDoubleMinValue))
              {
                return string.Format("Invalid /doubleminvalue: {0}", argument[1]);
              }
              break;
            case "/doubleprecision":
              if (!int.TryParse(argument[1], out argDoublePrecision))
              {
                return string.Format("Invalid /doubleprecision: {0}", argument[1]);
              }
              break;
            case "/doubleimemode":
              if (!Enum.TryParse<ImeMode>(argument[1], true, out argDoubleImeMode))
              {
                return string.Format("Invalid /doubleimemode: {0}", argument[1]);
              }
              break;
            case "/integermaxvalue":
              if (!int.TryParse(argument[1], out argIntegerMaxValue))
              {
                return string.Format("Invalid /integermaxvalue: {0}", argument[1]);
              }
              break;
            case "/integerminvalue":
              if (!int.TryParse(argument[1], out argIntegerMinValue))
              {
                return string.Format("Invalid /integerminvalue: {0}", argument[1]);
              }
              break;
            case "/integerformat":
              if (!Enum.TryParse<IntegerFormat>(argument[1], true, out argIntegerFormat))
              {
                return string.Format("Invalid /integerformat: {0}", argument[1]);
              }
              break;
            case "/memoimemode":
              if (!Enum.TryParse<ImeMode>(argument[1], true, out argMemoImeMode))
              {
                return string.Format("Invalid /memoimemode: {0}", argument[1]);
              }
              break;
            case "/memomaxlength":
              if (!int.TryParse(argument[1], out argMemoMaxLength))
              {
                return string.Format("Invalid /memomaxlength: {0}", argument[1]);
              }
              break;
            case "/moneymaxvalue":
              if (!double.TryParse(argument[1], out argMoneyMaxValue))
              {
                return string.Format("Invalid /moneymaxvalue: {0}", argument[1]);
              }
              break;
            case "/moneyminvalue":
              if (!double.TryParse(argument[1], out argMoneyMinValue))
              {
                return string.Format("Invalid /moneyminvalue: {0}", argument[1]);
              }
              break;
            case "/moneyimemode":
              if (!Enum.TryParse<ImeMode>(argument[1], true, out argMoneyImeMode))
              {
                return string.Format("Invalid /moneyimemode: {0}", argument[1]);
              }
              break;
            case "/moneyprecision":
              if (!int.TryParse(argument[1], out argMoneyPrecision))
              {
                return string.Format("Invalid /moneyprecision: {0}", argument[1]);
              }
              break;
            case "/moneyprecisionsource":
              if (!int.TryParse(argument[1], out argMoneyPrecisionSource))
              {
                return string.Format("Invalid /moneyprecisionsource: {0}", argument[1]);
              }
              break;
            case "/stringformat":
              if (!Enum.TryParse<StringFormat>(argument[1], true, out argStringFormat))
              {
                return string.Format("Invalid /stringformat: {0}", argument[1]);
              }
              break;
            case "/stringmaxlength":
              if (!int.TryParse(argument[1], out argStringMaxLength))
              {
                return string.Format("Invalid /stringmaxlength: {0}", argument[1]);
              }
              break;
            case "/stringimemode":
              if (!Enum.TryParse<ImeMode>(argument[1], true, out argStringImeMode))
              {
                return string.Format("Invalid /stringimemode: {0}", argument[1]);
              }
              break;
            default:
              return string.Format("Unknown parameter: {0}", argument[0]);
          }
        }
        else
        {
          return "Invalid parameters";
        }
      }

      if (string.IsNullOrEmpty(argURL))
      {
        return "Missing parameter: /url";
      }

      if (string.IsNullOrEmpty(argEntity))
      {
        return "Missing parameter: /entity";
      }

      if (string.IsNullOrEmpty(argAttribute))
      {
        return "Missing parameter: /attribute";
      }

      return string.Empty;
    }

    private static string Connection
    {
      get
      {
        StringBuilder connection = new StringBuilder();
        connection.Append("Url=");
        connection.Append(argURL);
        connection.Append(";");

        if (!string.IsNullOrEmpty(argUsername))
        {
          connection.Append("Username=");
          connection.Append(argUsername);
          connection.Append(";");
        }
        if (!string.IsNullOrEmpty(argPassword))
        {
          connection.Append("Password=\"");
          connection.Append(argPassword);
          connection.Append("\";");
        }
        if (!string.IsNullOrEmpty(argDomain))
        {
          connection.Append("Domain=");
          connection.Append(argDomain);
          connection.Append(";");
        }

        return connection.ToString();
      }
    }

    private static object GetAttribute(Entity entity, string attribute)
    {
      if (entity.Attributes.Contains(attribute))
      {
        return entity.Attributes[attribute];
      }
      else
      {
        return null;
      }
    }

    private static List<Conversion> AllowedConversions
    {
      get
      {
        if (allowedConversions.Count == 0)
        {
          allowedConversions.Add(new Conversion(AttributeTypeCode.Boolean, AttributeTypeCode.DateTime));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Boolean, AttributeTypeCode.Decimal));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Boolean, AttributeTypeCode.Double));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Boolean, AttributeTypeCode.Integer));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Boolean, AttributeTypeCode.Memo));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Boolean, AttributeTypeCode.Money));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Boolean, AttributeTypeCode.String));

          allowedConversions.Add(new Conversion(AttributeTypeCode.DateTime, AttributeTypeCode.Memo));
          allowedConversions.Add(new Conversion(AttributeTypeCode.DateTime, AttributeTypeCode.String));

          allowedConversions.Add(new Conversion(AttributeTypeCode.Decimal, AttributeTypeCode.Boolean));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Decimal, AttributeTypeCode.DateTime));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Decimal, AttributeTypeCode.Double));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Decimal, AttributeTypeCode.Integer));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Decimal, AttributeTypeCode.Memo));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Decimal, AttributeTypeCode.Money));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Decimal, AttributeTypeCode.String));

          allowedConversions.Add(new Conversion(AttributeTypeCode.Double, AttributeTypeCode.Boolean));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Double, AttributeTypeCode.DateTime));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Double, AttributeTypeCode.Decimal));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Double, AttributeTypeCode.Integer));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Double, AttributeTypeCode.Memo));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Double, AttributeTypeCode.Money));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Double, AttributeTypeCode.String));

          allowedConversions.Add(new Conversion(AttributeTypeCode.Integer, AttributeTypeCode.Boolean));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Integer, AttributeTypeCode.DateTime));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Integer, AttributeTypeCode.Decimal));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Integer, AttributeTypeCode.Double));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Integer, AttributeTypeCode.Memo));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Integer, AttributeTypeCode.Money));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Integer, AttributeTypeCode.String));

          allowedConversions.Add(new Conversion(AttributeTypeCode.Memo, AttributeTypeCode.Boolean));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Memo, AttributeTypeCode.DateTime));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Memo, AttributeTypeCode.Decimal));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Memo, AttributeTypeCode.Double));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Memo, AttributeTypeCode.Integer));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Memo, AttributeTypeCode.Money));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Memo, AttributeTypeCode.String));

          allowedConversions.Add(new Conversion(AttributeTypeCode.Money, AttributeTypeCode.Boolean));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Money, AttributeTypeCode.DateTime));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Money, AttributeTypeCode.Decimal));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Money, AttributeTypeCode.Double));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Money, AttributeTypeCode.Integer));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Money, AttributeTypeCode.Memo));
          allowedConversions.Add(new Conversion(AttributeTypeCode.Money, AttributeTypeCode.String));

          allowedConversions.Add(new Conversion(AttributeTypeCode.String, AttributeTypeCode.Boolean));
          allowedConversions.Add(new Conversion(AttributeTypeCode.String, AttributeTypeCode.DateTime));
          allowedConversions.Add(new Conversion(AttributeTypeCode.String, AttributeTypeCode.Decimal));
          allowedConversions.Add(new Conversion(AttributeTypeCode.String, AttributeTypeCode.Double));
          allowedConversions.Add(new Conversion(AttributeTypeCode.String, AttributeTypeCode.Integer));
          allowedConversions.Add(new Conversion(AttributeTypeCode.String, AttributeTypeCode.Memo));
          allowedConversions.Add(new Conversion(AttributeTypeCode.String, AttributeTypeCode.Money));
        }

        return allowedConversions;
      }
    }

    private class Conversion
    {
      private AttributeTypeCode from;
      private AttributeTypeCode to;

      public Conversion(AttributeTypeCode typeFrom, AttributeTypeCode typeTo)
      {
        this.from = typeFrom;
        this.to = typeTo;
      }

      public object ConvertValue(Entity entity, string attribute)
      {
        if (entity.Contains(attribute) && entity.Attributes[attribute] != null)
        {
          switch (From)
          {
            case AttributeTypeCode.Boolean:
              bool valueBoolean = (bool)entity.Attributes[attribute];
              switch (To)
              {
                case AttributeTypeCode.DateTime: return Convert.ToDateTime(valueBoolean, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Decimal: return Convert.ToDecimal(valueBoolean, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Double: return Convert.ToDouble(valueBoolean, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Integer: return Convert.ToInt32(valueBoolean, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Memo: return Convert.ToString(valueBoolean, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Money: return new Money(Convert.ToDecimal(valueBoolean, CultureInfo.CurrentCulture));
                case AttributeTypeCode.String: return Convert.ToString(valueBoolean, CultureInfo.CurrentCulture);
              }
              break;
            case AttributeTypeCode.DateTime:
              DateTime valueDateTime = (DateTime)entity.Attributes[attribute];
              switch (To)
              {
                case AttributeTypeCode.Memo: return Convert.ToString(valueDateTime, CultureInfo.CurrentCulture);
                case AttributeTypeCode.String: return Convert.ToString(valueDateTime, CultureInfo.CurrentCulture);
              }
              break;
            case AttributeTypeCode.Decimal:
              decimal valueDecimal = (decimal)entity.Attributes[attribute];
              switch (To)
              {
                case AttributeTypeCode.Boolean: return Convert.ToBoolean(valueDecimal, CultureInfo.CurrentCulture);
                case AttributeTypeCode.DateTime: return Convert.ToDateTime(valueDecimal, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Double: return Convert.ToDouble(valueDecimal, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Integer: return Convert.ToInt32(valueDecimal, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Memo: return Convert.ToString(valueDecimal, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Money: return new Money(Convert.ToDecimal(valueDecimal, CultureInfo.CurrentCulture));
                case AttributeTypeCode.String: return Convert.ToString(valueDecimal, CultureInfo.CurrentCulture);
              }
              break;
            case AttributeTypeCode.Double:
              double valueDouble = (double)entity.Attributes[attribute];
              switch (To)
              {
                case AttributeTypeCode.Boolean: return Convert.ToBoolean(valueDouble, CultureInfo.CurrentCulture);
                case AttributeTypeCode.DateTime: return Convert.ToDateTime(valueDouble, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Decimal: return Convert.ToDecimal(valueDouble, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Integer: return Convert.ToInt32(valueDouble, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Memo: return Convert.ToString(valueDouble, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Money: return new Money(Convert.ToDecimal(valueDouble, CultureInfo.CurrentCulture));
                case AttributeTypeCode.String: return Convert.ToString(valueDouble, CultureInfo.CurrentCulture);
              }
              break;
            case AttributeTypeCode.Integer:
              int valueInteger = (int)entity.Attributes[attribute];
              switch (To)
              {
                case AttributeTypeCode.Boolean: return Convert.ToBoolean(valueInteger, CultureInfo.CurrentCulture);
                case AttributeTypeCode.DateTime: return Convert.ToDateTime(valueInteger, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Decimal: return Convert.ToDecimal(valueInteger, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Double: return Convert.ToDouble(valueInteger, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Memo: return Convert.ToString(valueInteger, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Money: return new Money(Convert.ToDecimal(valueInteger, CultureInfo.CurrentCulture));
                case AttributeTypeCode.String: return Convert.ToString(valueInteger, CultureInfo.CurrentCulture);
              }
              break;
            case AttributeTypeCode.Memo:
            case AttributeTypeCode.String:
              string valueString = (string)entity.Attributes[attribute];
              switch (To)
              {
                case AttributeTypeCode.Boolean: return Convert.ToBoolean(valueString, CultureInfo.CurrentCulture);
                case AttributeTypeCode.DateTime: return DateTime.ParseExact(valueString, argDateTimePattern, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Decimal: return Convert.ToDecimal(valueString, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Double: return Convert.ToDouble(valueString, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Integer: return Convert.ToInt32(valueString, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Memo: return Convert.ToString(valueString, CultureInfo.CurrentCulture);
                case AttributeTypeCode.String: return Convert.ToString(valueString, CultureInfo.CurrentCulture);
              }
              break;
            case AttributeTypeCode.Money:
              decimal valueMoney = ((Money)entity.Attributes[attribute]).Value;
              switch (To)
              {
                case AttributeTypeCode.Boolean: return Convert.ToBoolean(valueMoney, CultureInfo.CurrentCulture);
                case AttributeTypeCode.DateTime: return Convert.ToDateTime(valueMoney, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Decimal: return Convert.ToDecimal(valueMoney, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Double: return Convert.ToDouble(valueMoney, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Integer: return Convert.ToInt32(valueMoney, CultureInfo.CurrentCulture);
                case AttributeTypeCode.Memo: return Convert.ToString(valueMoney, CultureInfo.CurrentCulture);
                case AttributeTypeCode.String: return Convert.ToString(valueMoney, CultureInfo.CurrentCulture);
              }
              break;
          }

          throw new InvalidCastException(string.Format("Cannot cast attribute from {0} to {1}", From, To));
        }
        else
        {
          return null;
        }
      }

      public AttributeTypeCode From
      {
        get
        {
          return this.from;
        }
      }

      public AttributeTypeCode To
      {
        get
        {
          return this.to;
        }
      }
    }

    private class EntityInstance
    {
      private Guid objectId;
      private object oldValue;
      private object newValue;
      private string fault;

      public EntityInstance(Guid objectId, object oldValue, object newValue, string fault)
      {
        this.objectId = objectId;
        this.oldValue = oldValue;
        this.newValue = newValue;
        this.fault = fault;
      }

      public bool HasFault
      {
        get
        {
          return !string.IsNullOrEmpty(this.fault);
        }
      }

      public Guid ObjectId
      {
        get
        {
          return this.objectId;
        }
      }

      public object OldValue
      {
        get
        {
          return this.oldValue;
        }
      }

      public object NewValue
      {
        get
        {
          return this.newValue;
        }
      }

      public string Fault
      {
        get
        {
          return this.fault;
        }
        set
        {
          this.fault = value;
        }
      }
    }
  }
}
